Skip to content

6. 规范化接口文档

📝 模块更新日志 新特性*

+ 新增 规范化文档 `Swagger` 支持配置自动在生产环境中启用登录 `UI` 配置(`LoginInfo.EnableOnProduction`) 4\.9\.5\.7 ⏱️2024\.09\.07 [e4020c2](https://gitee.com/dotnetchina/Furion/commit/e4020c21063d62c8408ee09f4e43fb2ea6fdfcaa)
+ 新增 规范化 `Swagger` 支持 `[SwaggerIgnore]` 特性忽略导出 4\.9\.3\.1 ⏱️2024\.05\.15 [75252a9](https://gitee.com/dotnetchina/Furion/commit/75252a9b7566c2d023c0f5581565af7be93fa363)
+ 新增 规范化 `Swagger` 的 `withProxy` 参数,解决二级虚拟目录部署或被代理出现 `404` 问题 4\.9\.3\.1 ⏱️2024\.05\.15 [\#I9PIIA](https://gitee.com/dotnetchina/Furion/issues/I9PIIA) [\#I9PHI8](https://gitee.com/dotnetchina/Furion/issues/I9PHI8)
+ 新增 规范化 `Swagger` 是否自动加载 `Xml` 注释文件配置 `EnableXmlComments` 4\.9\.3\.1 ⏱️2024\.05\.15 [d01bbaa](https://gitee.com/dotnetchina/Furion/commit/d01bbaae8dfcaad1bd03862070a3928c069afe72)
+ 新增 控制器/动态 `WebAPI` 方法添加 `[DisplayName]` 特性生成 `Swagger` 文档注释 4\.9\.2\.3 ⏱️2024\.03\.30 [0f24c66](https://gitee.com/dotnetchina/Furion/commit/0f24c66cfea3b0484aea97f2659f81a873324e0a)
+ 新增 规范化处理自动过滤 `SSE` 请求、文件请求、图片请求 4\.9\.1\.6 ⏱️2023\.11\.22 [\#I8IP6D](https://gitee.com/dotnetchina/Furion/issues/I8IP6D)
+ 新增 规范化文档枚举支持 `[EnumToNumber]` 特性配置生成前端枚举定义代码是字符串值还是整数值类型,默认为字符串值 4\.8\.8\.35 ⏱️2023\.07\.06 [\#I7IZ7S](https://gitee.com/dotnetchina/Furion/issues/I7IZ7S)
+ 新增 `Swagger` 分组信息可在任意配置文件中通过 `[openapi:分组名]` 进行配置 4\.8\.8\.26 ⏱️2023\.06\.20 [a70eed3](https://gitee.com/dotnetchina/Furion/commit/a70eed3ec5f3081fbdc08312fdb4770f39f27cc0)
+ 新增 `Swagger` 请求授权失败后自动退出授权状态 4\.8\.6\.12 ⏱️2023\.02\.20 [!717](https://gitee.com/dotnetchina/Furion/pulls/717)
+ 新增 `Swagger` 启用登录后配置 `CheckUrl` 可获取本地存储的 `Authorization` 请求报文头 4\.8\.6\.2 ⏱️2023\.02\.10 [\#I6E3LB](https://gitee.com/dotnetchina/Furion/issues/I6E3LB)
+ 新增 `Swagger` 支持复制路由地址功能 4\.8\.4\.13 ⏱️2023\.01\.11 [\#I5VNJI](https://gitee.com/dotnetchina/Furion/issues/I5VNJI)
  • 突破性变化

    • 调整 规范化结果 IUnifyResultProvider 接口,新增 OnAuthorizeException 方法 4.9.3 ⏱️2024.05.10 52d3c2c edc51f4

查看变化规范化接口 IUnifyResultProvider 添加了一个新的方法 OnAuthorizeException。此方法允许在 JWT 授权过程中出现异常时,提供统一的异常处理逻辑。

public interface IUnifyResultProvider  
{  
+    /// <summary>  
+    /// JWT 授权异常返回值  
+    /// </summary>  
+    /// <param name="context">HttpContext 上下文</param>  
+    /// <param name="metadata">异常元数据</param>  
+    /// <returns></returns>  
+    IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata);  

    // ...其他代码  
}  

实现代码:

[UnifyModel(typeof(YourRESTfulResult<>))]  
public class YourRESTfulResultProvider : IUnifyResultProvider  
{  
+    /// <summary>  
+    /// JWT 授权异常返回值  
+    /// </summary>  
+    /// <param name="context">HttpContext 上下文</param>  
+    /// <param name="metadata">异常元数据</param>  
+    /// <returns></returns>  
+    public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata)  
+    {  
+        return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)  
+            , UnifyContext.GetSerializerSettings(context));  
+    }  

    // ...其他代码  
}  

    • 调整 Swagger 文档注释逻辑,将 /// 注释方式优先级调整至最高,可覆盖 [DisplayName] 特性方式 4.9.2.17 ⏱️2024.04.14 ba5249c
  • 问题修复

    • 修复 规范化文档 Swagger 启用 EnableAnnotations(true, true) 无效问题 4.9.5.7 ⏱️2024.09.07 9b27cf4
    • 修复 规范化上下文处理中间件路由(无控制器情况)出现空异常问题 4.9.3.19 ⏱️2024.06.09 8f0968b
    • 修复 Swagger 授权后出现客户端 JavaScript 错误 4.9.3.18 ⏱️2024.06.08 9efa94f
    • 修复 项目名称包含数字且使用 <inheritdoc/> 继承注释时,Swagger 加载注释文件出现异常问题 4.9.3.17 ⏱️2024.06.04 857635d
    • 修复 因升级 Swashbuckle.AspNetCore6.6.1 导致 Swagger 无法自动设置授权信息问题 4.9.3.4 ⏱️2024.05.16 c68f6c8
    • 修复 因升级 Swashbuckle.AspNetCore6.6.1 导致 TypelnfoResolve 异常问题 4.9.3.3 ⏱️2024.05.16 64860b2 Admin.NET - #I9PMXH
    • 修复 规范化结果在未启用 401/403 等状态码中间件时进行了错误拦截 4.9.2.8 ⏱️2024.04.08 b135e8c
    • 修复 规范化文档加载文档注释时如果存在类完全限定名一致出现重复键异常问题 4.9.1.17 ⏱️2024.01.04 #I8TJZ0
    • 修复 Swagger 进行分组后 Tags 不能进行分组过滤问题 4.8.8.22 ⏱️2023.05.25 #I78A55
    • 修复 因 4.8.7.22 版本导致动态 WebAPI 类型注释丢失问题 4.8.7.27 ⏱️2023.03.29 #I6QM23
    • 修复 Swagger UI 不显示 ControllerBase 派生类注释 4.8.7.22 ⏱️2023.03.27 #I6QM23
    • 修复 启用规范化结果后导致 WebSocket 连接断开时出现异常 4.8.7.20 ⏱️2023.03.23 #I6PI5E
    • 修复 Swagger 接口排序同时指定 TagOrder 之后无效 4.8.7.2 ⏱️2023.03.01 #I6IQDI #I6IP66
    • 修复 规范化结果不带 mini-profiler 版本启动登录 UI 后不能传递 headers 问题 4.8.6.11 ⏱️2023.02.20 #I6G8IR
    • 修复 规范化结果不支持 OData 协议控制器 4.8.5.5 ⏱️2023.02.01 !571
    • 修复 启用 Swagger 登录功能之后不能触发响应拦截器 4.8.5.5 ⏱️2023.02.01 #I6C9A2 !702 !703
    • 其他调整

    • 调整 规范化文档枚举生成 json 格式,由 int32 改为 string 4.8.8.34 ⏱️2023.07.02 #I7HOPR

    • 调整 规范化文档默认 Title 解析规则,不再自动添加空格 4.8.8.26 ⏱️2023.06.20 24b7a47

6.1 什么是接口文档

在现在移动为王、多端互辅、前端百花齐放的开放时代,不再是一人包揽式开发,大家各司其职,后端工程师负责接口开发,前端负责接口联调,也就是互联网现在流行的前后端分离架构。

所以就需要由前后端工程师共同定义接口,编写接口文档,之后大家按照这个接口文档进行开发、维护及开放给第三方。

6.2 为什么要写接口文档

  • 能够让前端开发与后台开发人员更好的配合,提高工作效率
  • 项目迭代或者项目人员更迭时,方便后期人员查看和维护
  • 方便测试人员进行接口测试

6.3 为什么需要规范化文档

由于每个公司后端人员技术参差不齐,技术文档能力也不例外,导致接口定义及文档五花八门,不同项目或不同公司对接极其困难,而且体验糟糕。所以,无规矩不成方圆,为了开发人员间更好的配合,迫切需要整理出一套规范。

通常接口规范分为六个部分:

6.3.1 协议规范

为了确保不同系统/模块间的数据交互,需要事先约定好通讯协议,如:TCP、HTTP、HTTPS 协议。为了确保数据交互安全,建议使用 HTTPS 协议

6.3.2 接口路径规范

作为接口路径,为了方便清晰的区分来自不同的系统,可以采用不同系统/模块名作为接口路径前缀,如:支付模块:/pay/xxx,订单模块:/order/xxx

6.3.3 版本控制规范

为了便于后期接口的升级和维护,建议在接口路径中加入版本号,便于管理,实现接口多版本的可维护性。如:接口路径中添加类似"v1"、"v2"等版本号

6.3.4 接口命名规范

和 C# 命名规范一样,好的、统一的接口命名规范,不仅可以增强其可读性,而且还会减少很多不必要的口头/书面上的解释。可使用"驼峰命名法"按照实现接口的业务类型、业务场景等命名,有必要时可采取多级目录命名,但目录不宜过长,两级目录较为适宜

  • 常见命名方式:
    • 接口名称动词前/后缀化: 接口名称以接口数据操作的动词为前/后缀,常见动词有:Add、Delete、Update、Query、Get、Send、Save、Detail、List等,如:新建用户 AddUser、查询订单详情 QueryOrderDetail
    • 接口名称动词 + 请求方式:接口路径中包含具体接口名称的名词,接口数据操作动作以 HTTP 请求方式来区分。常用的 HTTP 请求方式有:
      • GET:从服务器取出资源(一项或多项)
      • POST:在服务器新建一个资源
      • PUT:在服务器更新资源(客户端提供改变后的完整资源)
      • PATCH:在服务器更新资源(客户端提供改变的属性)
      • DELETE:从服务器删除资源

6.3.5 请求参数规范

  • 请求方式:按照 GET、POST、PUT 等含义定义,避免出现不一致现象,对人造成误解、歧义
    • 请求头:请求头根据项目需求添加配置参数。如:请求数据格式,accept=application/json 等。如有需要,请求头可根据项目需求要求传入用户 token、唯一验签码等加密数据
    • 请求参数/请求体: 请求参数字段,尽可能与数据库表字段、对象属性名等保持一致,因为保持一致是最省事,最舒服的一件事

6.3.6 返回数据规范

统一规范返回数据的格式,对己对彼都有好处,此处以 json 格式为例。返回数据应包含:返回状态码、返回状态信息、具体数据返回数据中的状态码、状态信息,常指具体的业务状态,不建议和 HTTP 状态码混在一起。HTTP 状态,是用来体现 HTTP 链路状态情况,如:404-Not Found。HTTP 状态码和 json 结果中的状态码,并存尚可,用于体现不同维度的状态。

6.4 什么是 Swagger

相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。

其实无论是前端调用后端,还是后端调用后端,都期望有一个好的接口文档。但是这个接口文档对于程序员来说,就跟注释一样,经常会抱怨别人写的代码没有写注释,然而自己写起代码起来,最讨厌的,也是写注释。所以仅仅只通过强制来规范大家是不够的,随着时间推移,版本迭代,接口文档往往很容易就跟不上代码了。

发现了痛点就要去找解决方案。解决方案用的人多了,就成了标准的规范,这就是 Swagger 的由来

通过这套规范,你只需要按照它的规范去定义接口及接口相关的信息。再通过 Swagger 衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,生成多种语言的客户端和服务端的代码,以及在线接口调试页面等等。

这样,如果按照新的开发模式,在开发新版本或者迭代版本的时候,只需要更新 Swagger 描述文件,就可以自动生成接口文档和客户端服务端代码,做到调用端代码、服务端代码以及接口文档的一致性。

所以,Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful 风格的 Web 服务。

总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法、参数和模型紧密集成到服务器端的代码,允许 API 来始终保持同步。Swagger 让部署管理和使用功能强大的 API 从未如此简单。

6.5 Swagger 使用

Furion 框架提供了非常方便且灵活的 Swagger 配置,无需增加额外学习成本。

6.5.1 注册服务

小提示.AddInject[XXX]() 已经包含了 .AddSpecificationDocuments() 注册,无需再次注册

.UseInject() 已经包含了 .UseSpecificationDocuments() 注册,无需再次注册

Furion.Web.Core\Startup.cs

using Microsoft.AspNetCore.Builder;  
using Microsoft.AspNetCore.Hosting;  
using Microsoft.Extensions.DependencyInjection;  
using Microsoft.Extensions.Hosting;  

namespace Furion.Web.Core  
{  
    [AppStartup(800)]  
    public sealed class FurWebCoreStartup : AppStartup  
    {  
        public void ConfigureServices(IServiceCollection services)  
        {  
            services.AddSpecificationDocuments();  
            services.AddControllers();  
        }  

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
        {  
            // Other Codes  

            app.UseSpecificationDocuments();  

            app.UseEndpoints(endpoints =>  
            {  
                endpoints.MapControllers();  
            });  
        }  
    }  
}  

小知识services.AddSpecificationDocuments() 通常和 .AddDynamicApiControllers() 成对出现。

6.5.2 默认地址

Furion 框架中,默认 规范化文档 地址为 /api 目录,支持自定义配置

如下图所示:

可以通过两种方式配置:

  • app.UseInject("路由") 方式,如
app.UseInject("testapi"); // 那么  /testapi 就是规范化地址  

  • 配置文件配置
{  
  "SpecificationDocumentSettings": {  
    "RoutePrefix": "testapi"  
  }  
}  

配置文件优先级大于 UseInject() 方式

6.5.3 默认分组

Furion 框架中默认分组名为 Default支持自定义配置

{  
  "SpecificationDocumentSettings": {  
    "DefaultGroupName": "MyGroup"  
  }  
}  

6.5.4 文档注释

规范化文档默认扫描 Furion.ApplicationFurion.Web.CoreFurion.Web.Entry 三个程序集.xml 注释文件,支持自定义配置

只支持 /// 标识的注释语法,如:类、方法、属性、参数、返回值、验证特性

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    /// <summary>  
    /// 类注释  
    /// </summary>  
    public class FurionAppService : IDynamicApiController  
    {  
        /// <summary>  
        /// 方法注释  
        /// </summary>  
        /// <returns></returns>  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        /// <summary>  
        /// 带 ID 参数的方法注释  
        /// </summary>  
        /// <param name="id"></param>  
        /// <returns></returns>  
        public int Get(int id)  
        {  
            return id;  
        }  

        [DisplayName("生成注释")] // Furion 4.9.2.3+ 版本支持  
        public void SomeMethod()  
        {  
        }  

        /// <summary>  
        /// 如果同时存在显示这个  
        /// </summary>  
        [DisplayName("生成注释")] // Furion 4.9.2.3+ 版本支持  
        public void SomeMethod()  
        {  
        }  
    }  
}  

如下图所示:

小提示如果文档注释没有显示,请检查项目 属性->生成->输出 中 XML 文档是否配置输出路径。注意:只有不带路径的 【项目名称.xml】 才会自动加载。

<PropertyGroup>  
    <TargetFramework>net6.0</TargetFramework>  
    <DocumentationFile>Furion.Application.xml</DocumentationFile>  
</PropertyGroup>  

特别说明Debug 模式下和 Release 模式下的注释文件是不通用的,所以导致很多开发者发布到服务器上发现没有显示注释。我们只需要在 Visual Studio 中切换 Debug 模式为 Release,然后重新配置一次即可。

这样不管是 Debug 还是 Release 模式都会显示注释了。

6.5.5 多分组支持

多分组是一个系统中必备功能,我们可以将系统划分为多个模块,每个模块都独立的 api 配置。在 Furion 框架中,实现多分组非常简单。如:

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    [ApiDescriptionSettings("Group1")]  
    public class FurionAppService : IDynamicApiController  
    {  
        /// <summary>  
        /// 随父类 Group1 分组  
        /// </summary>  
        /// <returns></returns>  
        public string Post()  
        {  
            return nameof(Furion);  
        }  

        /// <summary>  
        /// 在 Group1、Group3 都有我  
        /// </summary>  
        /// <returns></returns>  
        [ApiDescriptionSettings("Group1", "Group3")]  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        /// <summary>  
        /// 我只在 Group2 出现  
        /// </summary>  
        /// <param name="id"></param>  
        /// <returns></returns>  
        [ApiDescriptionSettings("Group2")]  
        public int Get(int id)  
        {  
            return id;  
        }  
    }  
}  

如下图所示:

6.5.6 多分组排序

  • 方式一
  • 方式二

通过分组名称添加 @整数 进行排序

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    [ApiDescriptionSettings("Group1@1")]  
    public class FurionAppService : IDynamicApiController  
    {  
        public string Post()  
        {  
            return nameof(Furion);  
        }  

        [ApiDescriptionSettings("Group1", "Group3")]  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        [ApiDescriptionSettings("Group@2")]  
        public int Get(int id)  
        {  
            return id;  
        }  
    }  
}  

可以通过在分组名后面添加 @整数 进行排序,整数 越大排前面。如果分组名称多次指定且多次指定了 @整数 ,则自动取该分组最大的整数进行排序。

通过配置文件配置排序

Furion.Web.Entry/appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "GroupOpenApiInfos": [  
      {  
        "Group": "Group1",  
        "Order": 1  
      },  
      {  
        "Group": "Group2",  
        "Order": 2  
      },  
      {  
        "Group": "Group3",  
        "Order": 0  
      }  
    ]  
  }  
}  

如下图所示:

排序说明分组默认排序 Order0。如果同时配置了 @整数appsettings.json 配置文件,那么优先采用 appsettings.json 中的 Order

6.5.7 多分组信息配置

Furion 框架提供了可通过 appsettings.json 配置分组信息:

Furion.Web.Entry/appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "GroupOpenApiInfos": [  
      {  
        "Group": "Group1",  
        "Title": "分组标题",  
        "Description": "这里是分组描述",  
        "Version": "版本号",  
        "TermsOfService": "https://furion.net",  
        "Contact": {  
          "Name": "百小僧",  
          "Url": "https://gitee.com/monksoul",  
          "Email": "monksoul@outlook.com"  
        },  
        "License": {  
          "Name": "MIT",  
          "Url": "https://gitee.com/dotnetchina/Furion/blob/alpha/LICENSE"  
        }  
      }  
    ]  
  }  
}  

如下图所示:


版本说明以下内容仅限 Furion 4.8.8.26 + 版本使用。

自该版本开始,可以在任何配置文件中添加 [openapi:分组名] 作为 Key 进行配置,而无需在 SpecificationDocumentSettings:GroupOpenApiInfos 中指定。

如果两种方式同时配置,那么 GroupOpenApiInfos 中的属性值只有在 [openapi:分组名] 配置中存在才会覆盖,不存在则保留。

{  
  "[openapi:Group1]": {  
    "Title": "我是自定义的标题",  
    "Order": 10,  
    "Description": "我是自定义的描述",  
    "Version": "2.0.0",  
    "TermsOfService": "https://furion.net",  
    "Contact": {  
      "Name": "Furion.NET",  
      "Url": "https://gitee.com/monksoul",  
      "Email": "support@furion.net"  
    },  
    "License": {  
      "Name": "MIT",  
      "Url": "https://gitee.com/dotnetchina/Furion/blob/v4/LICENSE"  
    }  
  }  
}  

6.5.8 控制器和方法排序

有时候我们需要对控制器或者方法进行排序,框架提供了 [ApiDescriptionSettings] 特性的 Order 属性,其值越大排序越靠前,如:

[ApiDescriptionSettings(Order = 10)]  
public class FurionAppService : IDynamicApiController  
{  
    public string Post()  
    {  
        return nameof(Furion);  
    }  

    [ApiDescriptionSettings(Order = 5)]  
    public string Get()  
    {  
        return nameof(Furion);  
    }  

    [ApiDescriptionSettings(Order = 4)]  
    public int Get(int id)  
    {  
        return id;  
    }  
}  

[ApiDescriptionSettings(Order = 9)]  
public class Furion2AppService : IDynamicApiController  
{  
    // ...  
}  


排序说明最终输出到 Swagger 界面时,FurionAppServiceFurion2AppService 靠前,而 FurionAppService 中定义的方法排序时:Get > Get(int id) > Post

6.5.9 组中组(标签)

Tag 配置主要用于配置 Swagger 标签分组信息及合并标签。也就是 组中组:

  • 标签命名
  • 合并标签

未贴标签之前

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    public class FurionAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  

    public class TestAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  
}  

贴标签之后

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    [ApiDescriptionSettings(Tag = "分组一")]  
    public class FurionAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  

    [ApiDescriptionSettings(Tag = "分组二")]  
    public class TestAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  
}  

如下图所示:

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    [ApiDescriptionSettings(Tag = "合并所有标签")]  
    public class FurionAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  

    [ApiDescriptionSettings(Tag = "合并所有标签")]  
    public class TestAppService : IDynamicApiController  
    {  
        public string Get()  
        {  
            return nameof(Furion);  
        }  

        public int Get(int id)  
        {  
            return id;  
        }  
    }  
}  

如下图所示:

小知识如果 Tag 名字一样,则会自动合并,否则只是命名。

6.5.10 默认展开所有文档

Furion.Web.Entry/appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "DocExpansionState": "Full"  
  }  
}  

如下图所示:

DocExpansionState 配置说明:

  • List:列表式(展开子类),默认值
  • Full:完全展开
  • None:列表式(不展开子类)

6.5.11 配置文档标题

Furion.Web.Entry/appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "DocumentTitle": "我是自定义标题"  
  }  
}  

如下图所示:

6.5.12 授权控制

新版本 Furion 已经默认启用了 Bearer Token 授权配置,无需手动配置,如需手动配置,可手动添加以下类似配置:

{  
  "SpecificationDocumentSettings": {  
    "EnableAuthorized": true,  

    "SecurityDefinitions": [  
      {  
        "Id": "Bearer", // JWT 授权  
        "Type": "Http",  
        "Name": "Authorization",  
        "Description": "JWT Authorization header using the Bearer scheme.",  
        "BearerFormat": "JWT",  
        "Scheme": "bearer",  
        "In": "Header",  

        "Requirement": {  
          "Scheme": {  
            "Reference": {  
              "Id": "Bearer",  
              "Type": "SecurityScheme"  
            },  
            "Accesses": []  
          }  
        }  
      },  

      {  
        "Id": "basic", // Basic 授权  
        "Type": "Http",  
        "Name": "basic",  
        "Description": "Basic Authorization header using the username and password.",  
        "Scheme": "basic",  
        "In": "Header",  

        "Requirement": {  
          "Scheme": {  
            "Reference": {  
              "Id": "basic",  
              "Type": "SecurityScheme"  
            },  
            "Accesses": []  
          }  
        }  
      }  
    ]  
  }  
}  

6.5.13 在线测试

如下图所示:

6.5.14 性能监视 MiniProfiler

规范化文档默认集成了 MiniProfiler 第三方性能组件,通过该组件可以方便查看请求性能、异常堆栈、数据库操作等信息。默认在 Swagger 首页左上角显示。

如下图所示:

小提示也可以通过 appsettings.jsonAppSettings:InjectMiniProfiler 设为 false 关闭。

6.5.15 定义接口输出类型

using Furion.DynamicApiController;  
using Microsoft.AspNetCore.Mvc;  

namespace Furion.Application  
{  
    public class FurionAppService : IDynamicApiController  
    {  
        [ProducesResponseType(201, Type = typeof(TestDto))]  
        [ProducesResponseType(400)]  
        public string Get()  
        {  
            return nameof(Furion);  
        }  
    }  
}  

如下图所示:

6.5.16 隐藏特定分组

Furion 新版本提供了隐藏分组的 Visible 配置,设置为 false 之后该分组将不显示在规范化文档中,如:

appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "GroupOpenApiInfos": [  
      {  
        "Group": "Group1",  
        "Visible": false  
      }  
    ]  
  }  
}  

6.5.17 中文乱码问题

默认情况下,.json 文件并未采用 utf-8 编码,所以如果配置中文分组信息就会出现乱码情况,这时候,只需要修改 .json 文件编码为 utf-8 即可。

6.5.18 生产环境中关闭 Swagger

如果不需要线上环境开启 Swagger 功能,只需要在 appsettings.json 配置即可:

{  
  "AppSettings": {  
    "InjectSpecificationDocument": false  
  }  
}  

6.5.19 设置 Example Value 默认值

Swagger 会自动根据对象类型输入参数添加 Example Value 默认值,但是该默认值通常是对象属性的类型字符串或缺省值,如果我们需要自定义这些默认值,只需要添加 /// <example>默认值</example> 注释即可。

如:

/// <summary>  
/// 年龄  
/// </summary>  
/// <example>13</example>  
[Required, Range(10, 110)]  
public int Age { get; set; }  

如下图所示:

更多类型默认值设置示例:

  • bool 类型:/// <example>true</example>
  • string 类型:/// <example>foobar</example>
  • number 类型:/// <example>123</example>
  • null 类型: /// <example>null</example>
  • array 类型:/// <example>[ 1, 2, 3 ]</example>

关于 object 类型输入参数默认情况下,Example Value 不会显示 object 类型的对象属性,因为 Swagger 认为这是不合理的定义。如果需要强制显示,只需要添加 /// <example>"object"</example> 注释即可。

6.5.20 自定义 Swagger 配置

Furion 框架除了内置了不少配置以外,还提供了直接配置 Swagger Api 的参数,如:

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddInject(options =>  
    {  
        options.ConfigureSwaggerGen(gen =>  
        {  
           // ....  
        });  
    });  
}  

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
 {  
      // 如果使用 Furion 4.4.8+ 版本可以 app.UseInject(options => {}) 了,无需指定 configure:  
      app.UseInject(configure: options =>  
      {  
          options.ConfigureSwagger(swg =>  
          {  
              // ....  
          });  

          options.ConfigureSwaggerUI(ui =>  
          {  
              // ....  
          });  
      });  
}  


6.5.21 配置 SwaggerSchemaIds

Furion 框架默认只显示名称,如果需要自定义显示规则,只需要添加配置即可。

services.AddControllersWithViews()  
        .AddInject(options =>  
        {  
            options.ConfigureSwaggerGen(gen =>  
            {  
                gen.CustomSchemaIds(x => x.FullName);  
            });  
        });  

6.5.22 自定义 swagger.json 路由模板

默认情况下,Furion 框架会生成统一的分组模板,如:swagger/{documentName}/swagger.json{documentName} 会在运行时替换为分组名,如需自定义,配置 RouteTemplate 即可:

appsettings.json

{  
  "SpecificationDocumentSettings": {  
    "RouteTemplate": "myapp/{documentName}/xxxx.json"  
  }  
}  

6.5.23 关于 application/x-www-form-urlencoded 请求

默认情况下,Swagger 并未添加 application/x-www-form-urlencoded 支持,如需启用该配置,只需在方法顶部贴 [Consumes] 特性即可,如:

[Consumes("application/x-www-form-urlencoded")]  
public async Task<IActionResult> Test([FromForm] TestRequest testRequest)  
{  
  // ....  
}  

public class TestRequest  
{  
   public string TestValue { get; set; }  
}  

特别注意参数必须贴 [FromForm] 特性。另外请求时将参数按 URL 地址拼接并放在 Body 中请求。

6.5.24 Swagger 出现 CORS 问题解决

如果 Swagger 出现以下错误,如图:

则需要添加以下配置:

{  
  "SpecificationDocumentSettings": {  
    "HideServers": true  
  }  
}  

6.5.25 Swagger 出现默认 text/xml/text/plain 参数问题解决

产生此原因有两个必要条件:

  1. 使用了 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包并添加了 AddNewtonsoftJson() 注册。
  2. .AddNewtonsoftJson() 写在了 .AddInjectWithUnifyResult() 后面。

所以解决方法是,先注册 .AddNewtonsoftJson() 再注册规范化结果,如:

services.AddControllers()  
        .AddNewtonsoftJson()  
        .AddInjectWithUnifyResult();  

如果配置上述代码之后出现 application/json-patch+json 默认在第一位,那么可通过以下方式解决:

services.Configure<MvcOptions>(options =>  
{  
    options.InputFormatters.RemoveType(typeof(NewtonsoftJsonPatchInputFormatter));  
});  

6.5.26 Swagger 多语言支持

Furion 2.9.0 + 版本已经支持了 Swagger 文档地址 ?culture=en-US 参数多语言转发功能了,也就是 Swagger 地址带 ?culture= 参数将自动添加到每一个请求的 api 地址中。

6.5.27 自定义逻辑控制 Swagger 每一个 api 可见性

有时候我们需要自定义 Swagger 接口可见性,比如根据权限,不同用户类型,各种逻辑控制,如:

// 也可以用 .AddInjectWithUnifyResult  
services.AddInject(options =>  
{  
    options.ConfigureSwaggerGen(gen =>  
    {  
        gen.DocInclusionPredicate((currentGroup, apiDescription) =>  
        {  
            // Furion 内部检查,必须放第一行  
            var isShow = SpecificationDocumentBuilder.CheckApiDescriptionInCurrentGroup(currentGroup, apiDescription);  

            // 获取当前方法  
            _ = apiDescription.TryGetMethodInfo(out var method);  

            // 有了方法,这里做你想做的事情,isShow 设置 true 可见,设置 false 不可见  

            return isShow;  
        });  
    });  
});  

6.5.28 配置 MVC 控制器支持规范化处理

{  
  "UnifyResultSettings": {  
    "SupportMvcController": true  
  }  
}  

6.5.29 Swagger 刷新记住授权状态

默认情况下,Swagger 刷新浏览器后,授权状态将被重置,也就是需要重新登录,通过下面代码在 用户登录成功后 调用即可:

// ....验证用户名/密码....  

_httpContextAccessor.HttpContext.SigninToSwagger("传入 token");  

6.5.30 带登录的 Swagger 文档

版本说明以下内容仅限 Furion v3.3.3+ 版本使用。

默认情况下,Swagger 是任何人都可以访问的,这样也暴露出一些安全问题,所以在该版本之后添加了登录功能,只需要配置 SpecificationDocumentSettingsLoginInfo 即可:

{  
  "SpecificationDocumentSettings": {  
    "LoginInfo": {  
      "Enabled": true,  
      "CheckUrl": "/Home/CheckUrl", // 可以写任何自定义检查地址  
      "SubmitUrl": "/Home/SubmitUrl", // 可以写任何 自定义检查地址  
      "UserName": "admin",  
      "Password": "admin",  
      "EnableOnProduction": false // Furion 4.9.5.7+ 支持,是否在生产环境中自动开启,默认 false  
    }  
  }  
}  

配置说明

  • LoginInfo:配置 Swagger 是否需要登录才能访问,SpecificationLoginInfo 类型,默认 null仅在 Furion v3.3.3+` 有效
    • Enabled:是否启用登录授权,默认 false
    • CheckUrl:检查登录状态的 Url 地址,该地址必须是 POST 请求,已授权返回 200,否则返回 401,支持相对地址,以 / 开头
    • SubmitUrl:提交登录的 Url 地址,该地址必须是 POST 请求且只有一个 SpecificationAuth 类型参数,成功登录返回 200,否则返回 401,支持相对地址,以 / 开头

配置示例

  • ⭐️Controller(MVC)方式:

配置说明如果使用了 Controller 也就是 MVC,那么需要配置路由:

app.UseEndpoints(endpoints =>  
{  
    //  endpoints.MapControllers(); // 注释这个  

    endpoints.MapControllerRoute(  
        name: "default",  
        pattern: "{controller=Home}/{action=Index}/{id?}");  
});  

using Furion.SpecificationDocument;  
using Microsoft.AspNetCore.Authorization;  
using Microsoft.AspNetCore.Mvc;  
using System.ComponentModel.DataAnnotations;  

namespace Furion.Web.Entry.Controllers;  

public class HomeController : Controller  
{  
    [HttpPost, AllowAnonymous, NonUnify]  
    public int CheckUrl()  
    {  
        return 401;  
    }  

    [HttpPost, AllowAnonymous, NonUnify]  
    public int SubmitUrl([FromForm] SpecificationAuth auth)  
    {  
        // 读取配置信息  
        var userName = App.Configuration["SpecificationDocumentSettings:LoginInfo:UserName"];  
        var password = App.Configuration["SpecificationDocumentSettings:LoginInfo:Password"];  

        if (auth.UserName == userName && auth.Password == password)  
        {  
            return 200;  
        }  
        else  
        {  
            return 401;  
        }  
    }  
}  

配置地址:

{  
  "SpecificationDocumentSettings": {  
    "LoginInfo": {  
      "Enabled": true,  
      "CheckUrl": "/Home/CheckUrl", // 可以写任何自定义检查地址  
      "SubmitUrl": "/Home/SubmitUrl", // 可以写任何 自定义检查地址  
      "UserName": "admin",  
      "Password": "admin"  
    }  
  }  
}  

  • ⭐️WebAPI/动态API 方式
using Furion.SpecificationDocument;  

namespace MyProject.Application;  

public class SwaggerLoginServices : IDynamicApiController  
{  
    [HttpPost, AllowAnonymous, NonUnify]  
    public int CheckUrl()  
    {  
        return 401;  
    }  

    [HttpPost, AllowAnonymous, NonUnify]  
    public int SubmitUrl([FromForm] SpecificationAuth auth)  
    {  
        // 读取配置信息  
        var userName = App.Configuration["SpecificationDocumentSettings:LoginInfo:UserName"];  
        var password = App.Configuration["SpecificationDocumentSettings:LoginInfo:Password"];  

        if (auth.UserName == userName && auth.Password == password)  
        {  
            return 200;  
        }  
        else  
        {  
            return 401;  
        }  
    }  
}  


配置地址:

{  
  "SpecificationDocumentSettings": {  
    "LoginInfo": {  
      "Enabled": true,  
      "CheckUrl": "/api/swagger-login/check-url", // 可以写任何自定义检查地址  
      "SubmitUrl": "/api/swagger-login/url", // 可以写任何 自定义检查地址  
      "UserName": "admin",  
      "Password": "admin"  
    }  
  }  
}  

6.5.31 inheritdoc 实现注释继承

版本说明以下内容仅限 Furion v3.3.3+ 版本使用。

在过去我们在接口定义的时候编写了完整的成员注释,然后在派生成员中又要重复写一次,实际上做了很大无用功,现在 Furion 支持了注释继承了,同时 Swagger 中也能正确显示。

using Furion.DynamicApiController;  

namespace Furion.Application  
{  
    /// <inheritdoc cref="ITestInheritdoc" />  
    public class TestInheritdoc : ITestInheritdoc, IDynamicApiController  
    {  
        /// <inheritdoc cref="ITestInheritdoc.GetName"/>  
        public string GetName()  
        {  
            return "Furion";  
        }  

        /// <inheritdoc />  
        public string GetVersion()  
        {  
            return "3.3.3";  
        }  
    }  

    /// <summary>  
    /// 测试注释继承  
    /// </summary>  
    public interface ITestInheritdoc  
    {  
        /// <summary>  
        /// 获取名称  
        /// </summary>  
        /// <returns></returns>  
        string GetName();  

        /// <summary>  
        /// 获取版本  
        /// </summary>  
        /// <returns></returns>  
        string GetVersion();  
    }  
}  


显示效果:

注意事项<inheritdoc /> 不指定 cref 仅限成员可用且所在的类型必须指定 <inheritdoc cref="" />,这样才能自动识别。

6.5.32 启用 All Groups 分组功能

版本说明以下内容仅限 Furion v3.3.4+ 版本使用。

有时候我们为了更好的对接口进行归类,配置了 Swagger 多个分组的功能,但这样也对生成客户端请求代码造成了困扰,所以添加了新的配置:

{  
  "SpecificationDocumentSettings": {  
    "EnableAllGroups": true  
  }  
}  

6.5.33 接口过时控制

版本说明以下内容仅限 Furion v3.3.5+ 版本使用。

有时候我们某个接口已经过时,提示尽早调用最新接口,只需要在方法上面贴 [Obsolete] 即可,如:

[Obsolete("GetName() 已经过时,请调用 GetFrameworkName() 替代")]  
public string GetName()  
{  
  return nameof(Furion);  
}  

[Obsolete]  
public string Other()  
{  
  // ...  
}  

6.5.34 单一接口更多描述

版本说明以下内容仅限 Furion v3.3.5+ 版本使用。

在该版本新增了 [ApiDescriptionSettings]Description 属性,支持定义更多描述,如:

[ApiDescriptionSettings(Description = "我是一段描述,显示更多内容 <button>我是按钮</button>")]  
public string add()  
{  
  //....  
}  

6.5.35 Swagger 异常/不能显示/错误处理

有时候可能因为错误的配置导致 Swagger 不能显示,这时候只需要复制提示的错误 .json 链接地址到浏览器中访问即可,如:

https://localhost:你的端口/swagger/Default/swagger.json  

后面的 /swagger/Default/swagger.json 就是 Swagger 错误提示的 .json 链接地址。

这样就可以看到详细的错误了。

6.5.36 自定义 SwaggerSchemaId

版本说明以下内容仅限 Furion v3.6.4+ 版本使用。

有时候,不同程序集会定义相同的类型名称 Name,这样就会导致生成 SwaggerSchemaId 出现冲突,这时只需要在类型上贴 [SchemaId] 特性即可,如:

using Furion.SpecificationDocument;  

[SchemaId("Other_")]  
public class PersonDto  
{  
  // ...  
}  

  • SchemaIdAttribute 配置选项:
    • SchemaId:自定义 SchemaId,字符串类型,只能是 字母,数字,下划线 组合
    • Replace:是否完全替换,bool 类型,默认 false,默认是作为前缀拼接,如上面的 PersonDto 会生成 Other_PersonDto,如果设置为 true,则直接使用 Other_

6.5.37 自定义 SwaggerOperationId

版本说明以下内容仅限 Furion 4.1.7+ 版本使用。

通过我们根据 swagger.json 生成前端代码时,Swagger 会自动根据路由地址生成调用的 api 名称,但这样的名称往往不易读,这时候可自定义 [OperationId] 来配置生成的前端名称。

using Furion.SpecificationDocument;  

public class PersonDto  
{  
  [OperationId("MyClientMethodName")]  
  public string TestMethod()  
  {  
    // ...  
  }  
}  

6.5.38 Swagger 接口文档支持完整的 Markdown

Furion 最新版中,支持了完整的 Markdown 注册,如:

/// <summary>  
/// 测试 Markdown  
/// </summary>  
/// <remarks>  
/// # 先知 / Furion ([探索版](https://gitee.com/dotnetchina/Furion/tree/experimental/))  
///  
///     一个应用程序框架,您可以将它集成到任何.NET/C# 应用程序中。  
///  
/// An application framework that you can integrate into any.NET/C# application.  
///  
/// ## 安装 / Installation  
///  
/// - [Package Manager] (https://www.nuget.org/packages/Furion)  
///  
/// ```powershell  
/// Install-Package Furion  
/// ```  
///  
/// - [.NET CLI] (https://www.nuget.org/packages/Furion)  
///  
/// ```powershell  
/// dotnet add package Furion  
/// ```  
///  
/// ## 例子 / Examples  
///  
/// 我们在[主页](https://furion.net)上有不少例子,这是让您入门的第一个:  
///  
/// We have several examples [on the website] (https://furion.net). Here is the first one to get you started:  
///  
/// ```cs  
/// Serve.Run();  
///  
///     [DynamicApiController]  
///     public class HelloService  
///     {  
///         public string Say()  
///         {  
///             return "Hello, Furion";  
///         }  
///     }  
/// ```  
///  
/// 打开浏览器访问 `https://localhost:5001`。  
///  
/// Open browser access `https://localhost:5001`.  
///  
/// ## 文档 / Documentation  
///  
/// 您可以在[主页] (https://furion.net)或[备份主页](https://furion.net)找到 Furion 文档。  
///  
/// You can find the Furion documentation[on the website](https://furion.net) or [on the backup website](https://furion.net).  
///  
/// ## 贡献 / Contributing  
///  
/// 该存储库的主要目的是继续发展 Furion 核心,使其更快、更易于使用。 Furion 的开发在[Gitee](https://gitee.com/dotnetchina/Furion) 上公开进行,我们感谢社区贡献错误修复和改进。  
///  
/// 阅读[贡献指南] (https://furion.net/docs/contribute)内容,了解如何参与改进 Furion。  
///  
/// The main purpose of this repository is to continue evolving Furion core, making it faster and easier to use.Development of Furion happens in the open on[Gitee] (https://gitee.com/dotnetchina/Furion), and we are grateful to the community for contributing bugfixes and improvements.  
///  
/// Read[contribution documents] (https://furion.net/docs/contribute) to learn how you can take part in improving Furion.  
///  
/// ## 许可证 / License  
///  
/// Furion 采用[MIT](https://gitee.com/dotnetchina/Furion/blob/v4/LICENSE) 开源许可证。  
///  
/// Furion uses the[MIT] (https://gitee.com/dotnetchina/Furion/blob/v4/LICENSE) open source license.  
///  
/// ```  
/// Copyright© 2020-present 百小僧, Baiqian Co., Ltd.  
/// Furion is licensed under Mulan PSL v2.  
/// You can use this software according to the terms andconditions of the Mulan PSL v2.  
/// You may obtain a copy of Mulan PSL v2 at:  
///             https://gitee.com/dotnetchina/Furion/blob/v4/LICENSE  
/// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUTWARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
/// See the Mulan PSL v2 for more details.  
/// ```  
///  
/// </remarks>  
/// <returns></returns>  
public string Hello()  
{  
    return "Furion";  
}  

6.5.39 第三方 UI 集成,如 Knife4jUI

Furion 框架中,集成第三方 SwaggerUI 非常容易,比如集成 IGeekFan.AspNetCore.Knife4jUI

安装包只需要在 YourPoject.Web.Core 层安装 IGeekFan.AspNetCore.Knife4jUI 即可。

Knife4jUI 独立版本配置

var routePrefix = "api";    // 定义 swagger 路由地址,如果是跟目录,设置 string.Empty 即可  

app.UseKnife4UI(options =>  
{  
    options.RoutePrefix = routePrefix;  // 配置 Knife4UI 路由地址  
    foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())  
    {  
        options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);  
    }  
});  

app.UseInject(routePrefix); // 配置 Furion 路由地址  

Knife4jUISwagger 共存版本配置

app.UseKnife4UI(options =>  
{  
    options.RoutePrefix = "newapi";  // 配置 Knife4UI 路由地址,现在是 /newapi  
    foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())  
    {  
        options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);  
    }  
});  

app.UseInject();  // Furion 默认 api 地址为  /api  

如需实现登录之后自动将 token 添加到头部可在登录接口 AfterScript 执行以下代码:

ke.global.setAllHeader(  
  "Authorization",  
  "Bearer " + ke.response.headers["access-token"]  
);  

6.5.40 添加 Swagger 请求响应拦截

有时候我们希望在 Swagger 请求之前进行拦截,比如修改 Url 参数,添加额外请求头等,又或者想在响应之后打印返回值到控制台或进行其他操作。这时候可以通过以下方式处理,如:

app.UseInject(options =>  
{  
    options.ConfigureSwaggerUI(ui =>  
    {  
        // 请求拦截  
        ui.UseRequestInterceptor("function(request) { return defaultRequestInterceptor(request); }");  
        // 响应拦截  
        ui.UseResponseInterceptor("function(response) { return defaultResponseInterceptor(response); }");  
    });  
});  

注意,UseRequestInterceptorUseResponseInterceptor 代码不能换行且须在 defaultRequestInterceptor(request)defaultResponseInterceptor(response) 之前编写自定义代码,否则会报错。

下面给出请求添加自定义 header 示例:

app.UseInject(options =>  
{  
    options.ConfigureSwaggerUI(ui =>  
    {  
        // 添加请求头 framework: Furion,注意代码不能换行,否则 Swagger 无法加载 !!!!  
        ui.UseRequestInterceptor("function(request) { request.headers['framework']='Furion'; return defaultRequestInterceptor(request); }");  
    });  
});  

6.5.41 枚举类型值

版本说明以下内容仅限 Furion 4.8.8.35 + 版本使用。

默认情况下,枚举类型值会生成字符串类型,如需生成值类型则添加 [EnumToNumber] 特性,但还有一种情况,那就是如果枚举项存在中文,会强制生成为值类型

  • 缺省情况
public enum NoticeTypeEnum  
{  
    NOTICE = 1,  
    ANNOUNCEMENT = 2,  
}  

默认生成 TypeScript 代码:

export enum NoticeTypeEnum {  
  NOTICE = "NOTICE",  
  ANNOUNCEMENT = "ANNOUNCEMENT",  
}  

  • 配置情况
[EnumToNumber]  
public enum NoticeTypeEnum  
{  
    NOTICE = 1,  
    ANNOUNCEMENT = 2,  
}  

export enum NoticeTypeEnum {  
  NUMBER_1 = 1,  
  NUMBER_2 = 2,  
}  

这种的 NUMBER_ 规则 Furion 无法控制,这是 Swagger 生成的。 还可以设置 [EnumToNumber(false)] 生成字符串类型。

  • 存在中文情况
public enum NoticeTypeEnum  
{  
    NOTICE = 1,  
    ANNOUNCEMENT = 2,  
    中文  
}  

export enum NoticeTypeEnum {  
  NUMBER_1 = 1,  
  NUMBER_2 = 2,  
  NUMBER_3 = 3,  
}  

  • 除了上面独立配置以外还支持全局配置:
{  
  "SpecificationDocumentSettings": {  
    "EnumToNumber": true  
  }  
}  

6.5.42 运行时修改 Swagger UI 信息

有时候我们希望能够在运行时动态修改 Swagger UI 信息,这时候可以通过依赖注入 ISwaggerProvider 接口操作即可,如:

// 获取所有分组信息  
var openApiInfos = SpecificationDocumentBuilder.GetOpenApiGroups();  // 其中 Group 属性就是分组名  

// 通过依赖注入 ISwaggerProvider 接口  
var openApiDocument = _swaggerProvider.GetSwagger("Default"); // 获取 Default 分组名文档  

// 直接修改即可  
openApiDocument.Info.Title = "我是新标题";  
openApiDocument.Info.Description = "我是新描述";  

6.5.43 隐藏特定控制器或 Action

有时候我们不希望某个控制器或者 ActionSwagger 中显示,这时候可以贴 [ApiDescriptionSettings(false)] 特性即可。在 Furion 4.9.3.1+ 版本可以使用 [SwaggerIgnore] 新特性。

[ApiDescriptionSettings(false)]  
public void IgnoreSomeMethod()  
{   
}  

// Furion 4.9.3.1+ 支持   
[SwaggerIgnore] // [SwaggerIgnore] 特性支持类、方法、函数、参数、属性  
public void IgnoreSomeMethod()  
{   
}  

如果不希望生成路由,那么可贴 [NonAction] 特性或者选择非 public 修饰即可。

6.5.44 二级虚拟目录/Yarp/Nginx 代理 问题

版本说明以下内容仅限 Furion 4.9.3.1 + 版本使用。

在使用代理软件,如 Yarp/Nginx 时,可能会出现 Swagger UI 无法加载 swagger.json 的情况。这时候可通过 app.UseInject() 启用 withProxy: true 配置,如:

// 多种配置方式,取其一即可  
apply.UseInject(withProxy: true);  
apply.UseInject(string.Empry, withProxy: true);  
apply.UseInject("api", withProxy: true);  
apply.UseInject("api/abc", withProxy: true);  

6.6 SpecificationDocumentSettings 配置

除了上述例子外,Furion 提供了一些配置选项,如:

  • DocumentTitle:文档标题,string,默认 Specification Api Document
  • DefaultGroupName:默认分组名,string,默认 Default
  • EnableAuthorized:是否启用权限控制,bool,默认 true
  • FormatAsV2:采用 Swagger 2.0 版本,bool,默认 false 已弃用
  • RoutePrefix:规范化文档地址,string,默认 api如果希望在首页,改为空字符串即可
  • DocExpansionState:文档显示方式,DocExpansion,默认 List,取值:
    • List:列表式(展开子类),默认值
    • Full:完全展开
    • None:列表式(不展开子类)
  • XmlComments:程序集注释描述文件名(可带 .xmlstring,默认 Furion.Application, Furion.Web.Entry, Furion.Web.Core
  • GroupOpenApiInfos:分组信息配置,SpecificationOpenApiInfo[],默认 { 'Group': 'Default'}
  • SecurityDefinitions:安全策略定义配置,SpecificationOpenApiSecurityScheme[],默认 []
  • Servers:配置 Server 下拉列表,OpenApiServer[] 类型,默认 [],如:{Servers:[ { Url:"地址", Description:"描述"} ]}
  • HideServers:是否隐藏 Server 下拉列表,bool 类型,默认 true
  • RouteTemplate:配置文档 swagger.json 路由模板,默认模板:swagger/{documentName}/swagger.json, {documentName} 代表分组名,必须保留原样
  • PackagesGroups:配置模块化内置分组名称,string[] 类型,默认 []
  • EnableEnumSchemaFilter:启用枚举 Schema 筛选器,bool 类型,默认 true
  • EnableTagsOrderDocumentFilter:启用标签排序筛选器,bool 类型,默认 true
  • ServerDir:配置 IIS 添加 Application 部署名,string 类型,默认空,仅在 Furion v3.2.0+` 有效
  • LoginInfo:配置 Swagger 是否需要登录才能访问,SpecificationLoginInfo 类型,默认 null仅在 Furion v3.3.3+` 有效
    • Enabled:是否启用登录授权,默认 false
    • CheckUrl:检查登录状态的 Url 地址,该地址必须是 POST 请求,已授权返回 200,否则返回 401
    • SubmitUrl:提交登录的 Url 地址,该地址必须是 POST 请求且只有一个 SpecificationAuth 类型参数,成功登录返回 200,否则返回 401,支持相对地址,以 / 开头
  • EnableAllGroups:启用 Swagger 总分组功能,自动将所有分组的接口合并到 All Groups 中,bool 类型,默认 false仅在 Furion v3.3.4+` 有效
  • EnumToNumber:枚举类型生成值类型,bool 类型,默认 false仅在 Furion 4.8.8.35+` 有效
  • EnableXmlComments:是否自动加载 Xml 注释文件,bool 类型,默认 true仅在 Furion 4.9.3.1+` 有效

另外 SpecificationOpenApiInfo 内置配置如下:

  • Group:分组唯一标识,string 类型,必填
  • Order:分组排序,int 类型,数字越大排前面,默认 0
  • Visible:配置分组是否可见,bool 类型,默认 true
  • Title:配置分组标题,string 类型
  • Description:配置分组描述,string 类型
  • Version:配置分组版本,默认 1.0
  • TermsOfService:配置相关链接地址,Uri 类型
  • Contact:配置联系方式,OpenApiContact 类型
  • License:配置协议,OpenApiLicense 类型

配置示例:

{  
  "SpecificationDocumentSettings": {  
    "GroupOpenApiInfos": [  
      {  
        "Group": "Group1",  
        "Title": "分组标题",  
        "Description": "这里是分组描述",  
        "Version": "版本号",  
        "TermsOfService": "https://furion.net",  
        "Contact": {  
          "Name": "百小僧",  
          "Url": "https://gitee.com/monksoul",  
          "Email": "monksoul@outlook.com"  
        },  
        "License": {  
          "Name": "MIT",  
          "Url": "https://gitee.com/dotnetchina/Furion/blob/alpha/LICENSE"  
        }  
      }  
    ]  
  }  
}  

6.7 统一返回值模型/规范化结果/API 返回值

Furion 框架提供 规范化结果 功能,可以通过实现 IUnifyResultProvider 提供器实现统一规范化返回值定制,如:

  • 定义结果包装类型
// 必须是泛型类型  
public class YourRESTfulResult<T>  
{  
    /// <summary>  
    /// 状态码  
    /// </summary>  
    public int? StatusCode { get; set; }  

    /// <summary>  
    /// 数据  
    /// </summary>  
    public T Data { get; set; }  

    /// <summary>  
    /// 执行成功  
    /// </summary>  
    public bool Succeeded { get; set; }  

    /// <summary>  
    /// 错误信息  
    /// </summary>  
    public object Errors { get; set; }  

    /// <summary>  
    /// 附加数据  
    /// </summary>  
    public object Extras { get; set; }  

    /// <summary>  
    /// 时间戳  
    /// </summary>  
    public long Timestamp { get; set; }  
}  

  • 定义 IUnifyResultProvider 实现类,并贴特性 [UnifyModel(typeof(YourRESTfulResult<>))]
using Furion.DataValidation;  
using Furion.DependencyInjection;  
using Furion.UnifyResult.Internal;  
using Microsoft.AspNetCore.Http;  
using Microsoft.AspNetCore.Mvc;  
using Microsoft.AspNetCore.Mvc.Filters;  
using System;  
using System.Threading.Tasks;  

namespace YourProject.UnifyResult  
{  
    /// <summary>  
    /// RESTful 风格返回值  
    /// </summary>  
    [UnifyModel(typeof(YourRESTfulResult<>))]  
    public class YourRESTfulResultProvider  : IUnifyResultProvider  
    {  
        /// <summary>  
        /// JWT 授权异常返回值  
        /// </summary>  
        /// <param name="context"></param>  
        /// <param name="metadata"></param>  
        /// <returns></returns>  
        public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata) // Furion 4.9.3+ 支持  
        {  
            return new JsonResult(RESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)  
                , UnifyContext.GetSerializerSettings(context));  
        }  

        /// <summary>  
        /// 异常返回值  
        /// </summary>  
        /// <param name="context"></param>  
        /// <param name="metadata"></param>  
        /// <returns></returns>  
        public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)  
        {  
            return new JsonResult(YourRESTfulResult(metadata.StatusCode, data: metadata.Data, errors: metadata.Errors)  
                , UnifyContext.GetSerializerSettings(context)); // 当前行仅限 Furion 4.6.6+ 使用  
        }  

        /// <summary>  
        /// 成功返回值  
        /// </summary>  
        /// <param name="context"></param>  
        /// <param name="data"></param>  
        /// <returns></returns>  
        public IActionResult OnSucceeded(ActionExecutedContext context, object data)  
        {  
            return new JsonResult(YourRESTfulResult(StatusCodes.Status200OK, true, data)  
                , UnifyContext.GetSerializerSettings(context)); // 当前行仅限 Furion 4.6.6+ 使用  
        }  

        /// <summary>  
        /// 验证失败返回值  
        /// </summary>  
        /// <param name="context"></param>  
        /// <param name="metadata"></param>  
        /// <returns></returns>  
        public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)  
        {  
            return new JsonResult(YourRESTfulResult(metadata.StatusCode ?? StatusCodes.Status400BadRequest, data: metadata.Data, errors: metadata.ValidationResult) // 如果需要只显示第一条错误,修改为:errors: metadata.FirstErrorMessage  
                , UnifyContext.GetSerializerSettings(context)); // 当前行仅限 Furion 4.6.6+ 使用  
        }  

        /// <summary>  
        /// 特定状态码返回值  
        /// </summary>  
        /// <param name="context"></param>  
        /// <param name="statusCode"></param>  
        /// <param name="unifyResultSettings"></param>  
        /// <returns></returns>  
        public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)  
        {  
            // 设置响应状态码  
            UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);  

            // 默认情况下,并未设置 HTTP 状态码,所以返回为 200,如需设置可通过以下代码:  
            // context.Response.StatusCode = statusCode;  

            switch (statusCode)  
            {  
                // 处理 401 状态码  
                case StatusCodes.Status401Unauthorized:  
                    await context.Response.WriteAsJsonAsync(YourRESTfulResult(statusCode, errors: "401 Unauthorized")  
                        , App.GetOptions<JsonOptions>()?.JsonSerializerOptions);  
                    break;  
                // 处理 403 状态码  
                case StatusCodes.Status403Forbidden:  
                    await context.Response.WriteAsJsonAsync(YourRESTfulResult(statusCode, errors: "403 Forbidden")  
                        , App.GetOptions<JsonOptions>()?.JsonSerializerOptions);  
                    break;  
                default: break;  
            }  
        }  

        /// <summary>  
        /// 返回 RESTful 风格结果集  
        /// </summary>  
        /// <param name="statusCode"></param>  
        /// <param name="succeeded"></param>  
        /// <param name="data"></param>  
        /// <param name="errors"></param>  
        /// <returns></returns>  
        private static YourRESTfulResult<object> YourRESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)  
        {  
            return new YourRESTfulResult<object>  
            {  
                StatusCode = statusCode,  
                Succeeded = succeeded,  
                Data = data,  
                Errors = errors,  
                Extras = UnifyContext.Take(),  
                Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()  
            };  
        }  
    }  
}  

之后在 Startup.cs 中注册即可:

services.AddControllers()  
        .AddInjectWithUnifyResult<YourRESTfulResultProvider>();  

特别注意默认情况下,规范化结果不会对 401403404 状态码进行规范化处理,如需启动该状态码处理,只需在 Startup.cs 中的 Configure 中启用接口:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
    if (env.IsDevelopment())  
    {  
        app.UseDeveloperExceptionPage();  
    }  
    // 添加规范化结果状态码,需要在这里注册  
    app.UseUnifyResultStatusCodes();  // 如果和域登录冲突可设置参数 withAuthorizationHeaderCheck: false  
    // 其他注册...  
}  

6.7.1 排除规范化处理

有些时候,我们某些接口不需要进行规范化处理,这时,我们只需要帖 [NonUnify] 特性即可。

6.7.2 规范化结果添加额外数据

默认的规范化结果中包含 extras 属性,可以配置额外数据返回到客户端:

UnifyContext.Fill(new { Message = "操作成功" });  

6.7.3 自定义特别接口规范化结果

有些时候,我们特定接口需返回特定的接口类型,无需规范化处理,这时候我们只需要贴 [UnifyResult(typeof(结果类))][ProducesResponseType(typeof(结果类),200)],如:

[UnifyResult(typeof(Person))]  
public Person GetPerson(int id)  
{  
  // ...  
}  

6.8 支持多套规范化配置

版本说明以下内容仅限 Furion 4.4.4 + 版本使用。

在一些情况下,我们可能需要针对特定的控制器(动态 WebAPI)或特定的方法使用不同的规范化处理机制,这时就需要多套规范化处理提供器,如:

  1. 定义规范化处理提供程序
[UnifyModel(typeof(MyResult<>))]  
public class SpeciallyResultProvider : IUnifyResultProvider  
{  
    public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata) // Furion 4.9.3+ 支持  
    {  
        // 参考上面的规范化处理写法  
    }  

    public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)  
    {  
        // 参考上面的规范化处理写法  
    }  

    public IActionResult OnSucceeded(ActionExecutedContext context, object data)  
    {  
        // 参考上面的规范化处理写法  
    }  

    public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)  
    {  
        // 参考上面的规范化处理写法  
    }  

    public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)  
    {  
        // 参考上面的规范化处理写法  
    }  
}  

public class MyResult<T>  
{  
    /// <summary>  
    /// 数据  
    /// </summary>  
    public T Data { get; set; }  
}  

  1. 注册规范化处理提供器
services.AddUnifyProvider<SpeciallyResultProvider>("specially");  // 指定规范化唯一名称,如果不指定就会替代默认的  

  1. 在控制器/动态 WebAPI 中使用
public class TestUnifyProvider : IDynamicApiController  
{  
    public string DefaultUnify()  
    {  
        return "test";  
    }  

    [UnifyProvider]  
    public string DefaultUnify2()  
    {  
        return "test";  
    }  

    [UnifyProvider("specially")]  
    public string SpeciallyUnify()  
    {  
        return "特别";  
    }  
}  

6.9 针对特定控制器或特定方法配置序列化选项

很少开发者注意到 new JsonResult(data) 实际上有第二个参数的,也就是 new JsonResult(data, serializerSettings),那么可以根据自己的逻辑传递第二个参数,如果不传递则采用全局配置的序列化选项。

Furion 框架提供两种处理方式。

6.9.1 通过 JsonResult 设置第二个参数

这种方式比较原始化,代码比较繁杂,也不利于维护。

[NonUnify]  
public IActionResult SpecialApi()  
{  
    return new JsonResult(new YourRESTfulResult<object>  
    {  
        StatusCode = 200,  
        Succeeded = true,  
        Data = new  
        {  
            Name = "Furion"  
        },  
        Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()  
    }, new JsonSerializerOptions  
    {  
        PropertyNamingPolicy = null  
    });  
}  

6.9.2 注册多套序列化配置选项 (推荐)

版本说明以下内容仅限 Furion 4.6.6 + 版本使用。

Startup.cs 中注册一套新的规则:

services.AddUnifyJsonOptions("special", new JsonSerializerOptions // 如果使用 Newtonsoft.Json => new JsonSerializerSettings  
{  
    PropertyNamingPolicy = null  
});  

代码使用:

[UnifySerializerSetting("special")]  
public object SpecialApi()  
{  
    return new  
    {  
      Name = "Furion"  
    }  
}  

也可以手动返回 IActionResult 方式:

版本说明以下内容仅限 Furion 4.8.7.1 + 版本使用。

public IActionResult SpecialApi()  
{  
  return new JsonResult(new  
  {  
      Name = "Furion"  
  }, UnifyContext.GetSerializerSettings("special"));  
}  

特别提醒目前 Swagger 暂未提供个别的接口自定义 schema 序列化选项。

6.10 反馈与建议

与我们交流给 Furion 提 Issue